package com.gitee.wsl.func.filter.bloomfilter.extractor

import com.gitee.wsl.base.Objects
import com.gitee.wsl.func.Supplier
import com.gitee.wsl.func.consumer.Consumer
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilterExtractor
import com.gitee.wsl.func.predicate.Predicate
import com.gitee.wsl.text.format.format


/**
 * Implementation of the methods to manage the layers in a layered Bloom filter.
 *
 *
 * The manager comprises a list of Bloom filters that are managed based on
 * various rules. The last filter in the list is known as the `target` and
 * is the filter into which merges are performed. The Layered manager utilizes
 * three methods to manage the list.
 *
 *
 *  * ExtendCheck - A Predicate that if true causes a new Bloom filter to be
 * created as the new target.
 *  * FilterSupplier - A Supplier that produces empty Bloom filters to be used
 * as a new target.
 *  * Cleanup - A Consumer of a `LinkedList` of BloomFilter that removes any
 * expired or out dated filters from the list.
 *
 *
 *
 * When extendCheck returns `true` the following steps are taken:
 *
 *
 *  1. `Cleanup` is called
 *  1. `FilterSuplier` is executed and the new filter added to the list as
 * the `target` filter.
 *
 *
 *
 * @param <T> the [BloomFilter] type.
 * @since 4.5.0-M1
</T> */
class LayerManager<T : BloomFilter<T>> private constructor(
    filterSupplier: Supplier<T>, 
    extendCheck: Predicate<LayerManager<T>>,
    filterCleanup: Consumer<MutableList<T>>, 
    initialize: Boolean,
) : BloomFilterExtractor<T> {
    /**
     * Builds new instances of [LayerManager].
     *
     * @param <T> the [BloomFilter] type.
    </T> */
    class Builder<T : BloomFilter<T>>() : Supplier<LayerManager<T>> {
        private var extendCheck: Predicate<LayerManager<T>>
        private lateinit var supplier: Supplier<T>
        private var cleanup: Consumer<MutableList<T>>

        init {
            extendCheck = ExtendCheck.neverAdvance<T>()
            cleanup = Cleanup.noCleanup<T>()
        }

        /**
         * Builds the layer manager with the specified properties.
         *
         * @return a new LayerManager.
         */
        override fun get(): LayerManager<T> {
            return LayerManager(supplier, extendCheck, cleanup, true)
        }

        /**
         * Sets the Consumer that cleans the list of Bloom filters.
         *
         * @param cleanup the Consumer that will modify the list of filters removing out
         * dated or stale filters.
         * @return `this` instance.
         */
        fun setCleanup(cleanup: Consumer<MutableList<T>>): Builder<T> {
            this.cleanup = cleanup
            return this
        }

        /**
         * Sets the extendCheck predicate. When the predicate returns `true` a new
         * target will be created.
         *
         * @param extendCheck The predicate to determine if a new target should be
         * created.
         * @return `this` instance.
         */
        fun setExtendCheck(extendCheck: Predicate<LayerManager<T>>): Builder<T> {
            this.extendCheck = extendCheck
            return this
        }

        /**
         * Sets the supplier of Bloom filters. When extendCheck creates a new target,
         * the supplier provides the instance of the Bloom filter.
         *
         * @param supplier The supplier of new Bloom filter instances.
         * @return `this` instance.
         */
        fun setSupplier(supplier: Supplier<T>): Builder<T> {
            this.supplier = supplier
            return this
        }
    }

    /**
     * Static methods to create a Consumer of a MutableList of BloomFilter perform
     * tests on whether to reduce the collection of Bloom filters.
     */
    object Cleanup {
        /**
         * A Cleanup that never removes anything.
         *
         * @param <T> Type of BloomFilter.
         * @return A Consumer suitable for the LayerManager `cleanup` parameter.
        </T> */
        fun <T : BloomFilter<T>> noCleanup(): Consumer<MutableList<T>> {
            return Consumer { x: MutableList<T> -> }
        }

        /**
         * Removes the earliest filters in the list when the the number of filters
         * exceeds maxSize.
         *
         * @param <T> Type of BloomFilter.
         * @param maxSize the maximum number of filters for the list. Must be greater
         * than 0
         * @return A Consumer suitable for the LayerManager `cleanup` parameter.
         * @throws IllegalArgumentException if `maxSize <= 0`.
        </T> */
        fun <T : BloomFilter<T>> onMaxSize(maxSize: Int): Consumer<MutableList<T>> {
            require(maxSize > 0) { "'maxSize' must be greater than 0" }
            return Consumer { ll: MutableList<T> ->
                while (ll.size > maxSize) {
                    ll.removeFirst()
                }
            }
        }

        /**
         * Removes the last added target if it is empty.  Useful as the first in a chain
         * of cleanup consumers.  (e.g. `Cleanup.removeEmptyTarget.andThen( otherConsumer )`)
         *
         * @param <T> Type of BloomFilter.
         * @return A Consumer suitable for the LayerManager `cleanup` parameter.
        </T> */
        fun <T : BloomFilter<T>> removeEmptyTarget(): Consumer<MutableList<T>> {
            return Consumer { x: MutableList<T> ->
                if (!x.isEmpty() && x.last().isEmpty()) {
                    x.removeLast()
                }
            }
        }

        /**
         * Removes any layer identified by the predicate.
         *
         * @param <T> Type of BloomFilter.
         * @param test Predicate.
         * @return A Consumer suitable for the LayerManager `cleanup` parameter.
        </T> */
        fun <T : BloomFilter<T>> removeIf(test: Predicate<in T>): Consumer<MutableList<T>> {
            return Consumer { x: MutableList<T> -> x.removeAll { test.test(it) } }
        }
    }

    /**
     * A collection of common ExtendCheck implementations to test whether to extend
     * the depth of a LayerManager.
     */
    object ExtendCheck {
        /**
         * Creates a new target after a specific number of filters have been added to
         * the current target.
         *
         * @param <T> Type of BloomFilter.
         * @param breakAt the number of filters to merge into each filter in the list.
         * @return A Predicate suitable for the LayerManager `extendCheck` parameter.
         * @throws IllegalArgumentException if `breakAt <= 0`
        </T> */
        fun <T : BloomFilter<T>> advanceOnCount(breakAt: Int): Predicate<LayerManager<T>> {
            require(breakAt > 0) { "'breakAt' must be greater than 0" }
            return object : Predicate<LayerManager<T>> {
                var count: Int = 0

                override fun test(t: LayerManager<T>): Boolean {
                    if (++count == breakAt) {
                        count = 0
                        return true
                    }
                    return false
                }
            }
        }

        /**
         * Advances the target once a merge has been performed.
         *
         * @param <T> Type of BloomFilter.
         * @return A Predicate suitable for the LayerManager `extendCheck` parameter.
        </T> */
        fun <T : BloomFilter<T>> advanceOnPopulated(): Predicate<LayerManager<T>> {
            return Predicate { lm: LayerManager<T> -> !lm.last().isEmpty() }
        }

        /**
         * Creates a new target after the current target is saturated. Saturation is
         * defined as the `Bloom filter estimated N >= maxN`.
         *
         *
         * An example usage is advancing on a calculated saturation by calling:
         * `ExtendCheck.advanceOnSaturation(shape.estimateMaxN()) `
         *
         * @param <T> Type of BloomFilter.
         * @param maxN the maximum number of estimated items in the filter.
         * @return A Predicate suitable for the LayerManager `extendCheck` parameter.
         * @throws IllegalArgumentException if `maxN <= 0`
        </T> */
        fun <T : BloomFilter<T>> advanceOnSaturation(maxN: Double): Predicate<LayerManager<T>> {
            require(!(maxN <= 0)) { "'maxN' must be greater than 0" }
            return Predicate { manager: LayerManager<T> ->
                val bf = manager.last()
                maxN <= bf.shape.estimateN(bf.cardinality())
            }
        }

        /**
         * Does not automatically advance the target. @{code next()} must be called directly to
         * perform the advance.
         *
         * @param <T> Type of BloomFilter.
         * @return A Predicate suitable for the LayerManager `extendCheck` parameter.
        </T> */
        fun <T : BloomFilter<T>> neverAdvance(): Predicate<LayerManager<T>> {
            return Predicate { x: LayerManager<T> -> false }
        }
    }

    private val filters = mutableListOf<T>()

    private val filterCleanup: Consumer<MutableList<T>>

    private val extendCheck: Predicate<LayerManager<T>>

    private val filterSupplier: Supplier<T> = Objects.requireNonNull(filterSupplier, "filterSupplier")

    /**
     * Constructs a new instance.
     *
     * @param filterSupplier the non-null supplier of new Bloom filters to add the the list
     * when necessary.
     * @param extendCheck    The non-null predicate that checks if a new filter should be
     * added to the list.
     * @param filterCleanup  the non-null consumer that removes any old filters from the
     * list.
     * @param initialize     true if the filter list should be initialized.
     */
    init {
        this.extendCheck = Objects.requireNonNull(extendCheck, "extendCheck")
        this.filterCleanup = Objects.requireNonNull(filterCleanup, "filterCleanup")
        if (initialize) {
            addFilter()
        }
    }

    /**
     * Adds a new Bloom filter to the list.
     */
    private fun addFilter() {
        filters.add(Objects.requireNonNull(filterSupplier.get(), "filterSupplier.get() returned null."))
    }

    /**
     * Forces execution the configured cleanup without creating a new filter except in cases
     * where the cleanup removes all the layers.
     *
     * @see LayerManager.Builder.setCleanup
     */
    fun cleanup() {
        filterCleanup.accept(filters)
        if (filters.isEmpty()) {
            addFilter()
        }
    }

    /**
     * Removes all the filters from the layer manager, and sets up a new one as the
     * target.
     */
    fun clear() {
        filters.clear()
        addFilter()
    }

    /**
     * Creates a deep copy of this [LayerManager].
     *
     *
     * *Filters in the copy are deep copies, not references, so changes in the copy are NOT reflected in the original.*
     *
     *
     *
     * The `filterSupplier`, `extendCheck`, and the `filterCleanup` are shared between the copy and this instance.
     *
     *
     * @return a copy of this [LayerManager].
     */
    fun copy(): LayerManager<T> {
        val newMgr: LayerManager<T> = LayerManager<T>(filterSupplier, extendCheck, filterCleanup, false)
        for (bf in filters) {
            newMgr.filters.add(bf.copy())
        }
        return newMgr
    }

    /**
     * Gets the Bloom filter from the first layer.
     * No extension check is performed during this call.
     * @return The Bloom filter from the first layer.
     * @see .getTarget
     */
    fun first(): T? {
        return filters.first()
    }

    /**
     * Gets the Bloom filter at the specified depth. The filter at depth 0 is the
     * oldest filter.
     *
     * @param depth the depth at which the desired filter is to be found.
     * @return the filter.
     * @throws NoSuchElementException if depth is not in the range
     * [0,filters.size())
     */
    fun get(depth: Int): T? {
        if (depth < 0 || depth >= filters.size) {
            throw NoSuchElementException(String.format("Depth must be in the range [0,%s)", filters.size))
        }
        return filters[depth]
    }

    /**
     * Returns the number of filters in the LayerManager.  In the default LayerManager implementation
     * there is always at least one layer.
     *
     * @return the current depth.
     */
    val depth: Int
        get() = filters.size

    /**
     * Returns the current target filter. If a new filter should be created based on
     * `extendCheck` it will be created before this method returns.
     *
     * @return the current target filter after any extension.
     */
    val target: T?
        get() {
            if (extendCheck.test(this)) {
                next()
            }
            return last()
        }

    /**
     * Gets the Bloom filter from the last layer.
     * No extension check is performed during this call.
     *
     * @return The Bloom filter from the last layer.
     * @see .getTarget
     */
    fun last(): T {
        return filters.last()
    }

    /**
     * Forces an advance to the next depth. This method will clean-up the current
     * layers and generate a new filter layer. In most cases is it unnecessary to
     * call this method directly.
     *
     *
     * Ths method is used within [.getTarget] when the configured
     * `ExtendCheck` returns `true`.
     *
     *
     * @see LayerManager.Builder.setExtendCheck
     * @see LayerManager.Builder.setCleanup
     */
    fun next() {
        filterCleanup.accept(filters)
        addFilter()
    }

    /**
     * Executes a Bloom filter Predicate on each Bloom filter in the manager in
     * depth order. Oldest filter first.
     *
     * @param bloomFilterPredicate the predicate to evaluate each Bloom filter with.
     * @return `false` when the a filter fails the predicate test. Returns
     * `true` if all filters pass the test.
     */
     override fun processBloomFilters(bloomFilterPredicate: Predicate<T>): Boolean {
        for (bf in filters) {
            if (!bloomFilterPredicate.test(bf)) {
                return false
            }
        }
        return true
    }

    companion object {
        /**
         * Creates a new Builder with defaults of [ExtendCheck.neverAdvance] and
         * [Cleanup.noCleanup].
         *
         * @param <T> Type of BloomFilter.
         * @return A builder.
         * @see ExtendCheck.neverAdvance
         * @see Cleanup.noCleanup
        </T> */
        fun <T : BloomFilter<T>> builder(): Builder<T> {
            return Builder()
        }
    }
}


